﻿namespace Refract.OData
{
    using System;
    using System.Collections.Generic;
    using System.Data.Services.Client;
    using System.Linq;
    using System.Linq.Expressions;

    using Refract.Data;
    using Refract.MVVM;
    using Refract.Utility;

    public class ODataService<TContext, TEntity, TModel, TListEntity, TListModel> : ReadOnlyODataService<TContext, TEntity, TModel, TListEntity, TListModel>, IDataService<TEntity, TListEntity, TListModel>
        where TContext : DataServiceContext
        where TEntity : class, new()
        where TModel : Model<TEntity>, new()
        where TListEntity : ICollection<TEntity>, new()
        where TListModel : Model<TListEntity>, new()
    {
        #region Constructors

        public ODataService(TContext context)
            : base(context)
        {
        }

        #endregion Constructors

        #region Methods

        public void Add(TEntity entity)
        {
            throw new NotImplementedException();
        }

        public void CancelChanges()
        {
            throw new NotImplementedException();
        }

        public void Delete(TEntity entity)
        {
            throw new NotImplementedException();
        }

        public void Save()
        {
            throw new NotImplementedException();
        }

        #endregion Methods
    }

    /// <summary>
    /// A service that reads data from an OData service.
    /// </summary>
    /// <typeparam name="TContext">The type of the service context.</typeparam>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <typeparam name="TModel">The type of the model.</typeparam>
    /// <typeparam name="TListEntity">The type of the list entity.</typeparam>
    /// <typeparam name="TListModel">The type of the list model.</typeparam>
    public abstract class ReadOnlyODataService<TContext, TEntity, TModel, TListEntity, TListModel> : ReadOnlyDataService<TEntity, TListEntity, TListModel>
        where TContext : DataServiceContext
        where TEntity : class, new()
        where TModel : Model<TEntity>, new()
        where TListEntity : ICollection<TEntity>, new()
        where TListModel : Model<TListEntity>, new()
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ReadOnlyODataService&lt;TContext, TEntity, TModel, TListEntity, TListModel&gt;"/> class.
        /// </summary>
        /// <param name="context">The context.</param>
        protected ReadOnlyODataService(TContext context)
        {
            Context = context;
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// Gets or sets the context.
        /// </summary>
        /// <value>The context.</value>
        public TContext Context
        {
            get; set;
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Gets a list of all records for this entity.
        /// </summary>
        /// <returns></returns>
        public override TListModel GetList()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Gets the internal.
        /// </summary>
        /// <param name="query">The query.</param>
        /// <param name="filter">The filter.</param>
        /// <returns></returns>
        protected TModel GetInternal(DataServiceQuery<TEntity> query, Expression<Func<TEntity, bool>> filter = null)
        {
            var model = new TModel();
            var state = new Pair(query, model);

            if (filter != null)
            {
                query = (DataServiceQuery<TEntity>) query.Where(filter);
            }

            try
            {
                ProcessCount++;
                query.BeginExecute(OnLoadComplete, state);
            }
            catch (Exception exception)
            {
                throw new DataServiceClientException("Error loading a " + typeof (TEntity).Name + ".",
                                                     exception);
            }
            return model;
        }

        /// <summary>
        /// Internal method to gets a list of records.
        /// </summary>
        /// <param name="query">The query used to retrieve the list.</param>
        /// <returns></returns>
        protected virtual TListModel GetListInternal(DataServiceQuery<TEntity> query)
        {
            var model = new TListModel();

            try
            {
                ProcessCount++;
                var state = new Pair(query, model);
                query.BeginExecute(OnListLoadComplete, state);
            }
            catch (Exception exception)
            {
                throw new DataServiceClientException("Error loading a list of " + typeof (TEntity).Name + " entities.",
                                                     exception);
            }
            return model;
        }

        /// <summary>
        /// Called when list load operation is complete.
        /// </summary>
        /// <param name="ar">The ar.</param>
        protected virtual void OnListLoadComplete(IAsyncResult ar)
        {
            var state = ar.AsyncState as Pair;
            if (state == null) throw new NullReferenceException("state");

            var query = state.GetFirst<DataServiceQuery<TEntity>>();
            var model = state.GetSecond<TListModel>();

            IEnumerable<TEntity> result = query.EndExecute(ar);

            var entityList = new TListEntity();
            foreach (TEntity entity in result)
            {
                entityList.Add(entity);
            }

            if (model != null)
            {
                model.Data = entityList;
            }
            else
            {
                var action = state.GetSecond<Action<TListEntity>>();

                if (action != null)
                {
                    action(entityList);
                }
            }
            RaiseEntityListLoadComplete(entityList);
        }

        /// <summary>
        /// Called when load opertion is complete.
        /// </summary>
        /// <param name="ar">The ar.</param>
        private void OnLoadComplete(IAsyncResult ar)
        {
            var state = ar.AsyncState as Pair;
            if (state == null) throw new NullReferenceException("state");

            var query = state.GetFirst<DataServiceQuery<TEntity>>();
            var model = state.GetSecond<TModel>();

            TEntity entity = query.EndExecute(ar).Single();

            if (model != null)
            {
                model.Data = entity;
            }
            else
            {
                var action = state.GetSecond<Action<TEntity>>();
                if (action != null)
                {
                    action(entity);
                }
            }

            RaiseEntityLoadComplete(entity);
        }

        #endregion Methods
    }
}